package pt.uma.gap.ast;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.MethodRefParameter;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

/*
 * This class visit the Java AST nodes and parse the information accordingly.
 */

public class GAPVisitor extends ASTVisitor {

	//private AbstractCompilationUnitAnalysis cuAnalysis;

	
	private boolean insideMethod = false;
	private boolean insideFieldDec = false;
	private boolean insideAssignment = false;
	private boolean insideDeclaration = false;
	private int insideMethodInvocation = 0; // this is to used to analyze assignment. it helps us to parse some thing like
	//  a=new Class(new ClassA),  so now to make difference between these two new and for other reasons.

	private String space = ""; // just for logging
	 	
	private MethodASTInfo currentMethod = null; // whatever is inside method is stored in this list
	private ClassASTInfo currentClass = null; // what ever methods are in class are stored in this list
	private List<ClassASTInfo> classInfoList = new ArrayList<ClassASTInfo>(); //This will store all the class info
	
	// These two structure are used to generate graph, however we are not using them in parsing. These can be
	// consider as a repositories.
	
	public GAPVisitor() {
		super();
//		this.cuAnalysis = compilationUnitAnalysis;
	}
	
	// just for logging, does not use in parsing
	@Override
	public void preVisit(ASTNode node) {
		
		log2(space + "preVisit getNodeType: " + node.getNodeType()+ ", " + node.getClass() );
		
		space += "- "; 
		
		super.preVisit(node);
	}
	
	// just for logging, does not use in parsing
	@Override
	public void postVisit(ASTNode node) {
		
		space = space.substring(2); 
		log2(space + "postVisit getNodeType: " + node.getNodeType()+ ", " + node.getClass());
		super.postVisit(node);
	}
	
	// we parse and store info but no rules apply for this case
	@Override
	public void endVisit(PackageDeclaration node) {
		log(" -- endVisit PackageDeclaration -- "+node.getName());
		currentClass = new ClassASTInfo(node);
		getClassInfoList().add(currentClass);
		super.endVisit(node);
	}
	
	//This method is invoked when we find the class declaration
	@Override
	public boolean visit(TypeDeclaration node) {
		log(" -- visit TypeDeclaration -- " + node.getName());
		currentClass.setTypeDeclaration(node);
		return super.visit(node);
	}

	@Override
	public void endVisit(TypeDeclaration node) {
		log(" -- endVisit TypeDeclaration -- "+ node.getName());
		super.endVisit(node);
	}
	
	// This method is invoked when we find the assignment statement inside the method
	@Override
	public boolean visit(Assignment node) {
		insideAssignment = true;
		assert(insideMethodInvocation==0);
		//this indicates that we are dealing assignments and not the method invocations
		currentMethod.addAssignment(node);
		return super.visit(node);
	}

	@Override
	public void endVisit(Assignment node) {
		log(" -- endVisit Assignment -- ");
		log(1, "getLeftHandSide(): ", node.getLeftHandSide());
		log(1, "getRightHandSide(): ", node.getRightHandSide());
		log(1, "getOperator(): ", node.getOperator());
		
		List<AssignmentInfo> AssignmentInfoList=currentMethod.getAssignmentInfoList();
		int lastItem=currentMethod.getAssignmentInfoList().size() -1;
		AssignmentInfo aInfo=AssignmentInfoList.get(lastItem);
		EvaluateAssignment.evaluate(aInfo);
		
		insideAssignment = false;
		
	}
	
/*	

	@Override
	public void endVisit(CharacterLiteral node) {
		
		if(insideAssignment && insideMethodInvocation == 0){
			currentMethod.getAssignmentInfoList().
			get(currentMethod.getAssignmentInfoList().size() -1)
			.setAssignmentType(FlowType.VALUE_FLOW_RULE);
		}else if(insideDeclaration && insideMethodInvocation == 0){
			currentMethod.getVariableInfoList()
			.get(currentMethod.getVariableInfoList().size() -1)
			.setDeclarationType(FlowType.VALUE_FLOW_RULE);
		}
		
		super.endVisit(node);
	}



	@Override
	public void endVisit(NumberLiteral node) {
		
		if(insideAssignment && insideMethodInvocation == 0){
			
			List<AssignmentInfo> AssignmentInfoList=currentMethod.getAssignmentInfoList();
			
			int lastItem=currentMethod.getAssignmentInfoList().size() -1;
			
			AssignmentInfo aInfo=AssignmentInfoList.get(lastItem);
			aInfo.setAssignmentType(FlowType.VALUE_FLOW_RULE);
			
		}else if(insideDeclaration && insideMethodInvocation == 0){
			currentMethod.getVariableInfoList()
			.get(currentMethod.getVariableInfoList().size() -1)
			.setDeclarationType(FlowType.VALUE_FLOW_RULE);
		}
		
		super.endVisit(node);
	}

*/

	@Override
	public void endVisit(StringLiteral node) {
		if(insideMethodInvocation == 0){
			if(insideAssignment){
				currentMethod.getAssignmentInfoList().
				get(currentMethod.getAssignmentInfoList().size() -1)
				.setAssignmentType(FlowType.VALUE_FLOW_RULE);
			
			}else if(insideDeclaration){
				currentMethod.getVariableInfoList()
				.get(currentMethod.getVariableInfoList().size() -1)
				.setDeclarationType(FlowType.VALUE_FLOW_RULE);
			}
		}
		super.endVisit(node);
	}


// this method will be inviked when we find a new object
	@Override
	public void endVisit(ClassInstanceCreation node) {
		if(insideMethodInvocation == 0){
			if(insideAssignment){
				currentMethod.getAssignmentInfoList().
				get(currentMethod.getAssignmentInfoList().size() -1)
				.setAssignmentType(FlowType.NEW_FLOW_RULE);
			}else if(insideDeclaration){
				currentMethod.getVariableInfoList()
				.get(currentMethod.getVariableInfoList().size() -1)
				.setDeclarationType(FlowType.NEW_FLOW_RULE);
			}
		}
		super.endVisit(node);
	}
	
	// this will be invoked when we find a NULL on the R:H:S of the assignment
	@Override
	public void endVisit(NullLiteral node) {
		if(insideMethodInvocation == 0){
			if(insideAssignment){
				currentMethod.getAssignmentInfoList().
				get(currentMethod.getAssignmentInfoList().size() -1)
				.setAssignmentType(FlowType.NULL_ADDRESS_FLOW_RULE);
			}else if(insideDeclaration){
				currentMethod.getVariableInfoList()
				.get(currentMethod.getVariableInfoList().size() -1)
				.setDeclarationType(FlowType.NULL_ADDRESS_FLOW_RULE);
			}
		}
		super.endVisit(node);
	}
	
	// This method is invoked when it find the method declaration
	@Override
	public boolean visit(MethodDeclaration node) {
		insideMethod  = true;
		log(" -- visit MethodDeclaration -- "+node.getName() + ", "+ node.getReturnType2());
		log(1, "getName(): ", node.getName());
		currentMethod = new MethodASTInfo(node);
		currentClass.addMethodInfo(currentMethod);

		return super.visit(node);
	}
	@Override
	public void endVisit(MethodDeclaration node) {
		log(" -- endVisit MethodDeclaration -- "+ node.getName());

		insideMethod  = false;

	}
	
	// This method is invoked when we find a method invocation inside a method, but it has nothing to 
	// do with the method calls
	@Override
	public boolean visit(MethodInvocation node) {
		insideMethodInvocation++;
		return super.visit(node);
	}
	@Override
	public void endVisit(MethodInvocation node) {
		log(" -- endVisit MethodInvocation -- " + "getName(): " + node.getName() + " getExpression(): "+ node.getExpression());
		
		node.resolveMethodBinding();

		if(insideAssignment){ // x=getX();
			currentMethod.getAssignmentInfoList().
			get(currentMethod.getAssignmentInfoList().size() -1)
			.addMethodInvocation(node);
		}else if(insideDeclaration){ // Object X=getX();
			currentMethod.getVariableInfoList().
			get(currentMethod.getVariableInfoList().size() -1)
			.addMethodInvocation(node);
		}else{
			currentMethod.addMethodInvocation(node); //getX()
		}
		insideMethodInvocation--;
	}

	
	@Override
	//This method is used to get parametre
	public void endVisit(SingleVariableDeclaration node) {
		log(" -- endVisit SingleVariableDeclaration -- ");
		log(1,"getName(): ", node.getName());
//		log(1,"getExtraDimensions(): ", node.getExtraDimensions());
		log(1,"getInitializer(): ", node.getInitializer());
//		log(1,"getExtraDimensionsProperty(): ", node.getExtraDimensionsProperty());
//		log(1,"getInitializerProperty(): ", node.getInitializerProperty().getId());
//		log(1,"getNameProperty(): ", node.getNameProperty());
		log(1,"getType(): ", node.getType());
		
		if(insideMethod){
			currentMethod.addSingleVariableDeclaration(node);
		}
		
		super.endVisit(node);
	}

	@Override
	/*
	 * Variable declaration fragment AST node type, used in field declarations, 
	 * local variable declarations, and ForStatement initializers
	 */
	public void endVisit(VariableDeclarationFragment node) {
		log(" -- endVisit VariableDeclarationFragment -- ");
		log(1,"getName(): ", node.getName());
		//log(1,"getExtraDimensions(): ", node.getExtraDimensions());
		log(1,"getInitializer(): ", node.getInitializer());
		//log(1,"getExtraDimensionsProperty(): ", node.getExtraDimensionsProperty());
		//log(1,"getInitializerProperty(): ", node.getInitializerProperty().getId());
		//log(1,"getNameProperty(): ", node.getNameProperty());
		
		
		if(insideMethod){
			currentMethod.addVariableDeclarationFragment(node);
		}else if(insideFieldDec){
			currentClass.addVariableDeclarationFragment(node);
		}

		super.endVisit(node);
	}
	@Override
	public boolean visit(VariableDeclarationStatement node) {
		log(" -- endVisit VariableDeclarationStatement -- ");
		log(1,"getModifiers(): ", node.getModifiers());
		log(1,"getType(): ", node.getType());
		
		if(insideMethod){
			insideDeclaration = true;
			currentMethod.addVariableDeclarationStatement(node);
		}
		
		return super.visit(node);
	}


	@Override
	public void endVisit(VariableDeclarationStatement node) {

		if(insideMethod){
			insideDeclaration = false;
			EvaluateDeclaration.evaluate(
					currentMethod.getVariableInfoList().get(currentMethod.getVariableInfoList().size() -1)
					);
		}
		
		
		super.endVisit(node);
	}

	@Override
	public boolean visit(FieldDeclaration node) {
		insideFieldDec = true;
		return super.visit(node);
	}

	@Override
	public void endVisit(FieldDeclaration node) {
		log(" -- endVisit FieldDeclaration -- ");
		log(1,"getModifiers(): ", node.getModifiers());
		log(1,"getType(): ", node.getType());
		currentClass.addFieldDeclaration(node);
		
		List<VariableDeclarationFragment>list= node.fragments();
		VariableDeclarationFragment var=(VariableDeclarationFragment) list.get(0);
		String str=var.getName().toString();
	
		insideFieldDec = false;
		super.endVisit(node);
	}

	@Override
	public void endVisit(ArrayAccess node) {
		log(" -- endVisit ArrayAccess -- ");
		log(1, "node.getArray(): ", node.getArray());
		log(1, "node.getIndex(): ", node.getIndex());
//		this.cuAnalysis.getReporter().reportUserProblem(node.getArray() + " may be null.", node, cuAnalysis.getName());
	}

	public List<ClassASTInfo> getClassInfoList() {
		return classInfoList;
	}


	public void log(Object string) {
		System.out.println(string);
		
	}
	
	public void log(String string, Object name) {
		log(string + " " +  name);
		
	}
	
	public void log(int pos, String string, Object name) {
		String aux = "";
		for (int i = 0; i < pos; i++) {
			aux += "\t";
		}
		log(aux + string,  name);
		
	}


	private void log2(String string) {
		System.out.println(string);
		
	}
	
}
